/**
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 *   
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 */
package org.jiopi.module.mailsender;

import java.io.File;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Random;
import java.util.Map.Entry;
import javax.activation.FileDataSource;
import javax.activation.URLDataSource;
import org.jiopi.blueprint.mailsender.Mail;
import org.jiopi.blueprint.mailsender.MailAttachment;
import org.jiopi.blueprint.mailsender.MailSender;
import org.jiopi.blueprint.mailsender.SendMailException;
import org.jiopi.framework.annotation.module.SocketModule;
import org.apache.commons.mail.EmailAttachment;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.MultiPartEmail;
import org.apache.commons.mail.HtmlEmail;
import org.apache.commons.mail.Email;
import org.apache.commons.mail.SimpleEmail;

public class MailImpl implements Mail {
	
	@SocketModule(id="mailSender")
	protected MailSenderImpl mailSender;
	
	private String[] from;
	private String subject;
	private String text;
	private String html;
	private int mailType = Mail.TEXT_MAIL;
	private LinkedList<String[]> toList = new LinkedList<String[]>();
	private LinkedList<MailAttachment> attachments = new LinkedList<MailAttachment>();
	private LinkedHashMap<String,URL> embedURLList = new LinkedHashMap<String,URL>();
	private LinkedHashMap<String,File> embedFileList = new LinkedHashMap<String,File>();

	/**
	 * to Receiver, or receivers of the mail. must comply with RFC 2822
	 * @param to
	 */
	public void addTo(String to){
		String tos[] = to.split(",");
		for(String a:tos){
			a = a.trim();
			if(a.length()>0){
				toList.add(parseEmailAddress(a));
			}
		}
	}
	
	/**
	 * to Receiver
	 * @param email email address
	 * @param name  user name
	 */
	public void addTo(String email,String name){
		toList.add(new String[]{email,name});
	}
	
	/**
	 * mail from ,must comply with RFC 2822
	 * @param from
	 */
	public void setFrom(String from){
		this.from = parseEmailAddress(from);
	}
	
	/**
	 * mail from
	 * @param email email address
	 * @param name  user name
	 */
	public void setFrom(String email,String name){
		from = new String[]{email,name};
	}
	
	/**
	 * Set the email subject.
	 * @param subject
	 */
	public void setSubject(String subject){
		this.subject = subject;
	}
	
	/**
	 * Define the text content of the mail.
	 * 
	 * @param text
	 */
	public void setTextMsg(String text){
		this.text = text;
	}
	
	/**
	 * Define the html content of the mail.
	 * 
	 * @param text
	 */
	public void setHtmlMsg(String html){
		this.html = html;
	}
	
	/**
	 * 
	 * change the Mail Type
	 * 
	 * @param mailType
	 * 
	 * @see Mail#TEXT_MAIL
	 * @see Mail#HTML_MAIL
	 */
	public void changeMailType(int mailType){
		this.mailType = mailType;
	}
	
	/**
	 * Embeds an URL in the HTML.
	 * 
	 * @param url The URL of the file.
	 * @return  A String with the Content-ID of the URL.
	 */
	public String embed(URL url){
		String cid = randomAlphabetic(HtmlEmail.CID_LENGTH).toLowerCase();
		this.embedURLList.put(cid, url);
		return cid;
	}
	
	/**
	 * Embeds a File in the HTML.
	 * 
	 * @param file The file.
	 * @return  A String with the Content-ID of the URL.
	 */
	public String embed(File file){
		String cid = randomAlphabetic(HtmlEmail.CID_LENGTH).toLowerCase();
		this.embedFileList.put(cid, file);
		return cid;
	}
	
	/**
	 * Attach an EmailAttachment.
	 * 
	 * @param attachment
	 */
	public void attach(MailAttachment attachment){
		this.attachments.add(attachment);
	}
	
	/**
	 * Sends the email.
	 * @return the message id of the underlying MimeMessage
	 * @throws SendMailException the sending failed
	 */
	public String send() throws SendMailException{
		
		ClassLoader ccl = Thread.currentThread().getContextClassLoader();
		if(ccl!=null){
			Thread.currentThread().setContextClassLoader(MailImpl.class.getClassLoader());
		}
		try{
			Email email = null;
			if(this.mailType==Mail.HTML_MAIL){
				HtmlEmail mail = new HtmlEmail();
				setupCommon(mail);
				setupAttachment(mail);
				
				{//add embeds
					HtmlEmail tempMail = new HtmlEmail();
					for(Entry<String,URL> embedURL:this.embedURLList.entrySet()){
						String cid = embedURL.getKey();
						URL url = embedURL.getValue();
						String fileName = new File(url.getFile()).getName();
						tempMail.embed(url, fileName);
						mail.embed(new URLDataSource(url), fileName, cid);
					}
					
					for(Entry<String,File> embedFile:this.embedFileList.entrySet()){
						String cid = embedFile.getKey();
						File file = embedFile.getValue();
						String fileName = file.getName();
						tempMail.embed(file);
						mail.embed(new FileDataSource(file), fileName, cid);
					}
				}
				mail.setHtmlMsg(this.html);
				mail.setTextMsg(this.text);
				email = mail;
			}else{
				if(attachments.size()>0){
					MultiPartEmail mail = new MultiPartEmail();
					setupAttachment(mail);
					email = mail;
				}else{
					SimpleEmail mail = new SimpleEmail();
					email = mail;
				}
				email.setMsg(this.text);
				setupCommon(email);
			}
			
			return email.send();
		}catch(EmailException e){
			throw new SendMailException(e);
		}finally{
			if(ccl!=null)Thread.currentThread().setContextClassLoader(ccl);
		}
	}
	
	private void setupCommon(Email email) throws EmailException{
		if(mailSender.getCharset()!=null){
			email.setCharset(mailSender.getCharset());
		}
		email.setHostName(mailSender.getHostName());
		if(mailSender.getSmtpport()!=null){
			email.setSmtpPort(Integer.parseInt(mailSender.getSmtpport()));
		}
		email.setAuthentication(mailSender.getAuthenticationUserName(), mailSender.getAuthenticationPassword());
		for(String[] to:this.toList){
			email.addTo(to[0], to[1]);
		}
		if(this.from == null){
			this.from = parseEmailAddress(mailSender.getFrom());
		}
		email.setFrom(this.from[0],this.from[1]);
		email.setSubject(this.subject);
	}
	
	private void setupAttachment(MultiPartEmail email) throws EmailException{
		for(MailAttachment attachment:attachments){
			EmailAttachment emailAttachment = new EmailAttachment();
			emailAttachment.setDisposition(EmailAttachment.ATTACHMENT);
			if(attachment.file!=null)emailAttachment.setPath(attachment.file.getAbsolutePath());
			if(attachment.url!=null)emailAttachment.setURL(attachment.url);
			if(attachment.getDescription()!=null)emailAttachment.setDescription(attachment.getDescription());
			if(attachment.getName()!=null)emailAttachment.setName(attachment.getName());
			email.attach(emailAttachment);
		}
	}
	
	/**
	 * 
	 * @param mail
	 * @return 0: email 1: name or null if the given mail is not correct 
	 */
	protected static String[] parseEmailAddress(String mail){
		mail = mail.trim();
		int beginPos = mail.indexOf('<');
		if(beginPos>0){
			int endPos = mail.indexOf('>');
			if(endPos>beginPos){
				String name = mail.substring(0,beginPos).trim();
				String address = mail.substring(beginPos+1,endPos).trim();
				return new String[]{address,name};
			}
		}else{
			return new String[]{mail,null};
		}
		return null;
	}
	
	private static final Random RANDOM = new Random();
	/**
     * <p>
     * Creates a random string whose length is the number of characters specified.
     * </p>
     *
     * <p>
     * Characters will be chosen from the set of alphabetic characters.
     * </p>
     *
     * @param count the length of random string to create
     *
     * @return the random string
     *
     * @since Commons Lang v2.1, svn 201930
     */
    static String randomAlphabetic(int count)
    {
        return random(count, 0, 0, true, false, null, RANDOM);
    }

    /**
     * <p>
     * Creates a random string based on a variety of options, using supplied source of randomness.
     * </p>
     *
     * <p>
     * If start and end are both <code>0</code>, start and end are set to <code>' '</code> and <code>'z'</code>,
     * the ASCII printable characters, will be used, unless letters and numbers are both <code>false</code>,
     * in which case, start and end are set to <code>0</code> and <code>Integer.MAX_VALUE</code>.
     * </p>
     *
     * <p>
     * If set is not <code>null</code>, characters between start and end are chosen.
     * </p>
     *
     * <p>
     * This method accepts a user-supplied {@link Random} instance to use as a source of randomness. By seeding a
     * single {@link Random} instance with a fixed seed and using it for each call, the same random sequence of strings
     * can be generated repeatedly and predictably.
     * </p>
     *
     * @param count the length of random string to create
     * @param start the position in set of chars to start at
     * @param end the position in set of chars to end before
     * @param letters only allow letters?
     * @param numbers only allow numbers?
     * @param chars the set of chars to choose randoms from. If <code>null</code>,
     *              then it will use the set of all chars.
     * @param random a source of randomness.
     *
     * @return the random string
     *
     * @throws IllegalArgumentException if <code>count</code> &lt; 0.
     *
     * @since Commons Lang v2.1, svn 201930
     */
    private static String random(
        int count,
        int start,
        int end,
        boolean letters,
        boolean numbers,
        char [] chars,
        Random random)
    {
        if (count == 0)
        {
            return "";
        }
        else if (count < 0)
        {
            throw new IllegalArgumentException("Requested random string length " + count + " is less than 0.");
        }

        if ((start == 0) && (end == 0))
        {
            end = 'z' + 1;
            start = ' ';

            if (!letters && !numbers)
            {
                start = 0;
                end = Integer.MAX_VALUE;
            }
        }

        StringBuffer buffer = new StringBuffer();
        int gap = end - start;

        while (count-- != 0)
        {
            char ch;

            if (chars == null)
            {
                ch = (char) (random.nextInt(gap) + start);
            }
            else
            {
                ch = chars[random.nextInt(gap) + start];
            }

            if ((letters && numbers && Character.isLetterOrDigit(ch)) || (letters && Character.isLetter(ch))
                            || (numbers && Character.isDigit(ch)) || (!letters && !numbers))
            {
                buffer.append(ch);
            }
            else
            {
                count++;
            }
        }

        return buffer.toString();
    }

}
